#pragma once

#include <boost/operators.hpp>
#include <boostext/exception.hpp>
#include <boostext/format.hpp>

#include "exports.hpp"
#include "Glyph.hpp"




/**
 * A CharGlyph contains additional information for a specific character
 * on the source UI texture, as a character may be offset'ed by a certain
 * amount of pixels.
 */
class ENGINE_UI_API CharGlyph : public Glyph
{
protected:

	uint16   m_id;
	int2     m_off;
	uint8    m_xAdv;
	uint8    m_chnl;

public:

	CharGlyph(QDomElement glyph, QDir path);
	CharGlyph(int2 pos, intsize2 size, uint16 id, int2 off, uint8 xAdv, uint8 chnl);
	virtual ~CharGlyph();



	/**
	 * Get the offsets for this character.
	 */
	int2 offset() const;

	/**
	 * Get the total number of pixels, that should
	 * be advanced, when printing this character.
	 */
	uint8 xAdvance() const;



	/**
	* Obtain a quad for this character glyph.
	* The quad resides at (0,0) and can be moved to it's destination.
	*/
	virtual Quad quad() const;

	virtual QDomElement domElement(QDomDocument& document) const;
};
///////////////////////////////////////////////////////////////////////////////////////////////

struct HAlignment
{
	enum Type
	{
		Left,
		Center,
		Right,
		Justify
	};
};
///////////////////////////////////////////////////////////////////////////////////////////////

struct VAlignment
{
	enum Type
	{
		Top,
		Center,
		Bottom
	};
};
///////////////////////////////////////////////////////////////////////////////////////////////

struct HFlow
{
	enum Type
	{
		LeftToRight, //< Default Horizontal
		RightToLeft, //< Not implemented yet
	};
};
///////////////////////////////////////////////////////////////////////////////////////////////

struct VFlow
{
	enum Type
	{
		TopToBottom, //< Default Verticall
		BottomToTop, //< Not implemented yet
	};
};
///////////////////////////////////////////////////////////////////////////////////////////////




/**
 * A KerningPair contains information about an additional offset, that
 * is applied to a character's width, when rendering multiple characters.
 */
struct ENGINE_UI_API KerningPair
{
	QChar   left;
	QChar   right;

	int8    offset;

	KerningPair();
	KerningPair(QChar left, QChar right,int8 offset);
	KerningPair(QDomElement pair);

	virtual QDomElement domElement(QDomDocument& document) const;
};
///////////////////////////////////////////////////////////////////////////////////////////////




/**
 * This little structure describes certain font properties.
 * Each font contains such 
 */
class ENGINE_UI_API FontDesc
	: public boost::totally_ordered<FontDesc>
{
public:

	typedef boost::optional<FontDesc>   opt;

public:

	FontDesc();
	FontDesc(QDomElement font);
	FontDesc(QString name, uint8 size, uint8 outline, bool bold, bool italic);

	/**
	 * Compare this FontDesc against another one.
	 */
	bool operator==(const FontDesc& that) const;

	/**
	 * Compare this FontDesc against another one.
	 */
	bool operator<(const FontDesc& that) const;

	virtual QDomElement domElement(QDomDocument& document) const;

public:

	QString   name;
	uint8     size;
	uint8     outline;
	bool      bold;
	bool      italic;
};
///////////////////////////////////////////////////////////////////////////////////////////////






/**
 * CharGlyphCollection contains all glyphs, that are needed
 * for a font with certain properties, for example size, boldness,
 * italicy and outline thickness.
 */
template <typename CharGlyph_Type, typename KerningPair_Type>
class Font
{
public:

	typedef CharGlyph_Type            CharGlyph_t;
	typedef KerningPair_Type          KerningPair_t;
	typedef QMap<QChar,CharGlyph_t>   CharGlyphs;
	typedef QVector<KerningPair_t>    KerningPairs;

protected:

	FontDesc       m_desc;

	CharGlyphs     m_glyphs;
	QChar          m_defaultGlyph;

	KerningPairs   m_kerningPairs;

public:

	Font()
	{}

	/**
	 * Create a font from a dom element.
	 */
	Font(QDomElement font, QDir path)
		: m_desc(font)
	{
		QDomElement chars = font.firstChildElement("characters");
		if(chars.isNull())
			BOOSTEXT_THROW(exception("missing 'characters' element in 'font' element"));

		QDomElement ch = chars.firstChildElement("char");
		while(!ch.isNull())
		{
			QChar charId = ch.attribute("id").toUInt();
			m_glyphs.insert(charId, CharGlyph_t(ch, path));
			ch = ch.nextSiblingElement("char");
		}

		QDomElement pairs = font.firstChildElement("kerningpairs");
		if(pairs.isNull())
			BOOSTEXT_THROW(exception("missing 'kerningpairs' element in 'font' element"));

		m_kerningPairs.reserve(pairs.childNodes().size());
		QDomElement p = pairs.firstChildElement("pair");
		while(!p.isNull())
		{
			m_kerningPairs << KerningPair_t(p);
			p = p.nextSiblingElement("pair");
		}
	}

	virtual ~Font()
	{}



	/**
	 * Get the description of the font.
	 */
	const FontDesc& desc() const
	{
		return m_desc;
	}

	/**
	 * Set the description for the font.
	 */
	void setDesc(const FontDesc& desc)
	{
		m_desc = desc;
	}



	/**
	* Get the glyph, associated with the given character.
	* If there is no such character, the default is being returned instead.
	*/
	const CharGlyph_t& glyph(QChar character) const
	{
		CharGlyphs::const_iterator i = m_glyphs.find(character);
		if(i == m_glyphs.end())
			i = m_glyphs.find(m_defaultGlyph);

		if(i == m_glyphs.end())
			BOOSTEXT_THROW(programming_error("There is no default character for this font"));

		return *i;
	}

	/**
	 * Returns the x-offset that is to be applied to the right when both the left
	 * and right character are rendered in sequence.
	 * Returns 0 when there is no special offset for them.
	 */
	int8 offset(QChar left, QChar right) const
	{
		foreach(const KerningPair_t& p, m_kerningPairs)
		{
			if(p.left == left && p.right == right)
				return p.offset;
		}

		// There is no such character, so we don't need a special offset
		return 0;
	}


	/**
	 * Adds a glyph to the collection.
	 */
	void add(QChar character, const CharGlyph_t& glyph)
	{
		CharGlyphs::const_iterator i = m_glyphs.find(character);
		if(i != m_glyphs.end())
			BOOSTEXT_THROW(exception(format("There is already a glyph for character '%1%'") % character.unicode()));

		m_glyphs.insert(character, glyph);
	}

	/**
	 * Adds a kerning pair to the collection.
	 */
	void add(const KerningPair_t& pair)
	{
		m_kerningPairs << pair;
	}

	/**
	* Calculate the size the given text would occupy.
	* When specifying HAlignment::Left, the returned vector contains the maximum width(x)
	* to the right.
	* When specifying HAlignment::Center, the returned vector contains half the maximum width(x)
	* to the right, which means the resulting text would need the same space to the left as well.
	* When specifying HAlignment::Right, the returned contains the maximum width(x) to the left.
	* In all cases, the returned vector's y component.
	*/
	intsize2 calculateSize(QString text, HAlignment::Type alignment = HAlignment::Left) const
	{
		intsize2 csize;

		int i = 0;
		int n = 0;
		while((i = text.indexOf('\n', n)) != -1 || n < text.size())
		{
			// There may not be a single linebreak in the text, so i is -1
			// However we need it to point to the end of the substring to render, which
			// is why we set i to size()
			if(i == -1)
				i = text.size();

			// The vertical size increases by the font's size every row...
			// it's as simple as that
			csize.height += m_desc.size;
			int w = calculateWidth(text.midRef(n, i-n));

			switch(alignment)
			{
			case HAlignment::Left:
			case HAlignment::Right:
				// The font goes to the right or the left
				csize.width = std::max(csize.width, w);
				break;
			case HAlignment::Center:
				// The font is centered, we pick the right
				csize.width = std::max(csize.width, w/2);
				break;
			}

			n = ++i;
		}

		return csize;
	}

	/**
	* Calculate the height of the given text.
	*/
	int calculateHeight(QString text) const
	{
		return calculateHeight(QStringRef(&text));
	}

	/**
	 * Render the given text at the given position.
	 *
	 * The alignments explained in detail:
	 * Depending on several input parameters the position is treated differently.
	 * HAlignment::Left: The text is aligned to the y-axis of the position and goes to the right side (+x)
	 * HAlignment::Center: The text is centered around the positions y-axis
	 * HAlignment::Right: The text is aligned to the y-axis of the position and goes to the left side (-x)
	 *
	 * The flows explained in detail:
	 * VFlow::TopToBottom: The text is aligned to the x-axis of the position and goes downwards (+y)
	 * VFlow::BottomToTop: The text is aligned to the x-axis of the positions and goes upwards (-y)
	 * HFlow::LeftToRight: The text will go in the direction, indicated by the alignment
	 * HFlow::RightToLeft: The text will go in the direction, indicated by the alignment
	 */
	void render(Quads& quads,
				int2 pos,
				const math::color& color,
				const QString& text,
				HAlignment::Type horizontalAlignment = HAlignment::Left,
				VFlow::Type verticalFlow = VFlow::TopToBottom,
				HFlow::Type horizontalFlow = HFlow::LeftToRight) const
	{
		int i = 0;
		int n = 0;
		while((i = text.indexOf('\n', n)) != -1 || n < text.size())
		{
			// There may not be a single linebreak in the text, so i is -1
			// However we need it to point to the end of the substring to render, which
			// is why we set i to size()
			if(i == -1)
				i = text.size();

			int2 p = pos;

			// Depending on the horizontal alignment, adustments have to me made to render a row
			switch(horizontalAlignment)
			{
			case HAlignment::Right:  p.x -= calculateWidth(text.midRef(n, i-n)); break;
			case HAlignment::Center: p.x -= calculateWidth(text.midRef(n, i-n)) / 2; break;
			}

			renderRow(quads, text.midRef(n, i-n), p, color, verticalFlow);

			// Advance the position in the direction, indicated by the vertical flow
			switch(verticalFlow)
			{
			case VFlow::TopToBottom: pos.y += m_desc.size; break;
			case VFlow::BottomToTop: pos.y -= m_desc.size; break;
			default: BOOSTEXT_THROW(boostext::invalid_value_exception(verticalFlow));
			}

			// Jump over the '\n' and take the next character
			n = ++i;
		}
	}

	virtual QDomElement domElement(QDomDocument& document) const
	{
		QDomElement font = m_desc.domElement(document);

		QDomElement characters = document.createElement("characters");
		foreach(const CharGlyph_t& glyph, m_glyphs)
		{
			characters.appendChild(glyph.domElement(document));
		}


		QDomElement kerningpairs = document.createElement("kerningpairs");
		foreach(const KerningPair& pair, m_kerningPairs)
		{
			kerningpairs.appendChild(pair.domElement(document));
		}

		font.appendChild(characters);
		font.appendChild(kerningpairs);
		return font;
	}

	/**
	 * Get the number of characters of the given text, that will be visualized.
	 * This equals the number of quads that is created, when calling render().
	 *
	 * @see drawnChar
	 */
	static int count(QString text)
	{
		int n = 0;
		for(int i = 0; i< text.size(); ++i)
		{
			if(drawnChar(text.at(i)))
				++n;
		}

		return n;
	}

protected:

	/**
	 * Calculate the height of the given text.
	 */
	int calculateHeight(QStringRef text) const
	{
		int height = text.size() ? m_desc.size : 0;

		// Go over the entire range of characters and calculate the height
		for(int i = 0; i < text.size(); ++i)
		{
			QChar charId = text.at(i);
			if(charId == '\n')
				height += m_desc.size;
		}

		return height;
	}

	/**
	 * Calculate the width of the given text.
	 */
	int calculateWidth(QStringRef text) const
	{
		int width = 0;

		// Go over the entire range of characters and calculate the width
		for(int i = 0; i < text.size();)
		{
			QChar charId = text.at(i);

			if(drawnChar(charId))
			{
				// Get the glyph for this character (if there is one)
				// or simply return the default glyph.
				const CharGlyph_t& ch = glyph(charId);

				width += ch.xAdvance();

				// Apparently, we add the offset to the next character...
				// It differs, depending on the character pair, to be rendered
				if(++i != text.size())
					width += offset(charId, text.at(i));
			}
			else
			{
				// This character won't be visualized...
				++i;
			}
		}

		return width;
	}

	/**
	 * Render the given number of characters as a row.
	 */
	void renderRow(Quads& quads,
				   const QStringRef& text,
				   int2 pos,
				   const math::color& col,
				   VFlow::Type verticalFlow) const
	{
		int2 width;

		// We may need to adjust the position, depending on the verticalFlow
		if(verticalFlow == VFlow::BottomToTop)
		{
			// In this mode, the first line is rendered beneath the second line (and so on),
			// so the position should denote the most lower part of the text, but we use the position
			// as a marker for the most upper part of the line. So we simply shift the position, to
			// take that into account
			pos.y -= m_desc.size;
		}

		for(int i = 0; i < text.size();)
		{
			QChar charId = text.at(i);

			if(drawnChar(charId))
			{
				// Obtain the glyph for that character and create
				// a quad that is shifted to it's position
				const CharGlyph_t& ch = glyph(charId);
				Quad q = ch.quad();
				q.setColor(col);
				q.move(pos + width);
				quads.push_back(q);

				// The next character is rendered that many units to the right from this one.
				width.x += ch.xAdvance();

				if(++i != text.size())
					width.x += offset(charId, text.at(i));
			}
			else
			{
				// This char won't be visualized
				++i;
			}
		}
	}

	/**
	 * Test if the given characters is getting drawn or not.
	 * This doesn't test if it's contained within the font's description,
	 * but if there will be a quad for this specific character, when
	 * calling render().
	 */
	static bool drawnChar(QChar c)
	{
		uint16 s = c.unicode();
		// Both \r and \n will not be visualized
		return s != '\t' && s != '\r' && s != '\n';
	}
};
///////////////////////////////////////////////////////////////////////////////////////////////

typedef Font<CharGlyph,KerningPair>   BasicFont;
template class ENGINE_UI_API          Font<CharGlyph,KerningPair>;
